#include <iostream>
#include <getopt.h>
#ifdef __linux__
#include <iconv.h>
#endif
#include <Ice/Ice.h>
#include <boost/filesystem/operations.hpp>
#include <log4cxx/logger.h>
#include <log4cxx/propertyconfigurator.h>
#include <log4cxx/appender.h>
#include "UVSSIce.h"
#include "XmlConfig.h"
#include "ANPRTH.h"
#include "CameraBase.h"
#include "FFmpegCamera.h"
#include "HikCamera.h"
#include "ANPRCamera.h"

#define INIT_ANPR_CAMERA 1
#define OPEN_ANPR_CAMERA 2
#define SAVE_ANPR_IMAGE 3
#define TEST_ANPR_CAMERA 4
#define CAMERA_INDEX 5
#define WINDOW_ID 6
#define IMAGE_PATH 7
#define ANPR_MODE 8
#define CAMERA_TYPE 9
#define CAMERA_IP_ADDRESS 10
#define CAMERA_USERNAME 11
#define CAMERA_PASSWORD 12
#define CAMERA_PATH 13
#define START_SAVE_VIDEO 14
#define STOP_SAVE_VIDEO 15
#define VIDEO_PATH 16

std::string configPath;
log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger("UVSSVideoCamera"));
int cameraIndex = 0;
int windowId = 0;
std::string imagePath = "";
int anprMode = 0;
std::string videoPath = "";
ANPRCamera *anprCamera = 0;
bool imageSaved = false;
ANPRTH *anpr = 0;

class UVSSI : public UVSSIce::UVSS
{
public:
    virtual void ANPRInfoCallback(int port, const std::string &lpNumber, const std::string &imagePath, const Ice::Current&)
    {
        LOG4CXX_INFO(logger, "UVSSI::ANPRInfoCallback(): port: " << port << ", lpNumber: " << lpNumber << ", imagePath: " << imagePath);
    }
};

class UVSSVideoCameraI : public UVSSIce::UVSSVideoCamera
{
public:
    virtual void InitANPRCamera(int index, int windowId, const Ice::Current & = Ice::Current());
    virtual int OpenANPRCamera(int index, const Ice::Current & = Ice::Current());
    virtual int CloseANPRCamera(int index, const Ice::Current & = Ice::Current());
    virtual int SaveANPRImage(int cameraIndex, int mode, const std::string &imagePath = "", const Ice::Current & = Ice::Current());
    virtual int StartSaveVideo(int cameraIndex, const std::string &videoPath = "", const Ice::Current & = Ice::Current());
    virtual int StopSaveVideo(int cameraIndex, const Ice::Current & = Ice::Current());
};

void UVSSVideoCameraI::InitANPRCamera(int cameraIndex, int windowId, const Ice::Current &)
{
    LOG4CXX_INFO(logger, "UVSSVideoCameraI::InitANPRCamera(): cameraIndex: " << cameraIndex << ", windowsId: " << windowId);

#ifdef _WIN32
    anprCamera->Init(cameraIndex, (HWND)windowId);
#else
    anprCamera->Init(cameraIndex, windowId);
#endif
}

int UVSSVideoCameraI::OpenANPRCamera(int cameraIndex, const Ice::Current &)
{
    anprCamera->Connect(cameraIndex);

    return 0;
}

int UVSSVideoCameraI::CloseANPRCamera(int cameraIndex, const Ice::Current &)
{
    anprCamera->Disconnect(cameraIndex);

    return 0;
}

int UVSSVideoCameraI::SaveANPRImage(int cameraIndex, int mode, const std::string &imagePath, const Ice::Current &)
{
    LOG4CXX_INFO(logger, "SaveANPRImage(): cameraIndex: " << cameraIndex << ", mode: " << mode << ", imagePath: " << imagePath);

    int ret = -1;

    if (mode == 0)
    {
        anprCamera->imagePath[cameraIndex] = imagePath;
        anprCamera->captureImage = cameraIndex;

        ret = 0;
    }
    else if (mode == 1)
    {
        if (!imagePath.empty())
        {
            cv::imwrite(imagePath.c_str(), cv::cvarrToMat(anprCamera->image[cameraIndex]));
        }
        else if (!anprCamera->imagePath[cameraIndex].empty())
        {
            std::string path;

            if (anprCamera->enableOutputPath)
                path = anprCamera->outputPath + "/" + anprCamera->imagePath[cameraIndex];
            else
                path = anprCamera->imagePath[cameraIndex];

            cv::imwrite(path.c_str(), cv::cvarrToMat(anprCamera->image[cameraIndex]));
        }
    }

    return ret;
}

int UVSSVideoCameraI::StartSaveVideo(int cameraIndex, const std::string &videoPath, const Ice::Current &)
{
    LOG4CXX_INFO(logger, "StartSaveVideo(): cameraIndex: " << cameraIndex << ", videoPath: " << videoPath);

    return anprCamera->StartSaveVideo(cameraIndex, videoPath.c_str());
}

int UVSSVideoCameraI::StopSaveVideo(int cameraIndex, const Ice::Current &)
{
    LOG4CXX_INFO(logger, "StopSaveVideo(): cameraIndex: " << cameraIndex);

    return anprCamera->StopSaveVideo(cameraIndex);
}

int SendToIceClient(int port, const char *lpNumber, const char* imagePath)
{
    Ice::CommunicatorPtr uvssVideoCameraIch = 0;
    Ice::ObjectAdapterPtr uvssVideoCameraAdapter = 0;
    Ice::ObjectPtr uvssVideoCameraObject = 0;

    configPath = boost::filesystem::initial_path<boost::filesystem::path>().string() + "/UVSSVideoCamera.xml";

    try
    {
        std::string uvssVideoCameraIPAddress = "127.0.0.1";
        std::string uvssIPAddress = "127.0.0.1";
        int uvssVideoCameraPort = 10000;
        int uvssPort = 10001;

        XmlConfig config;

        if (config.Open(configPath.c_str()))
        {
            uvssVideoCameraIPAddress = config.Get("RPC", "UVSSVideoCameraIceIPAddress", "127.0.0.1");
            uvssIPAddress = config.Get("RPC", "UVSSIceIPAddress", "127.0.0.1");
            uvssVideoCameraPort = config.GetIntValue("RPC", "UVSSVideoCameraIcePort", 10000);
            uvssPort = config.GetIntValue("RPC", "UVSSIcePort", 10001);
        }

        // Ice client
        Ice::CommunicatorPtr ich = Ice::initialize();

        auto base = ich->stringToProxy("UVSS:tcp -h " + uvssIPAddress + " -p " + std::to_string(uvssPort));

        auto proxy = UVSSIce::UVSSPrx::uncheckedCast(base);

        if (!proxy)
        {
            throw std::runtime_error("Invalid UVSS proxy");

            return -1;
        }

        proxy->ANPRInfoCallback(port, lpNumber, imagePath);
    }
    catch (const std::exception& e)
    {
        LOG4CXX_ERROR(logger, "SendToIceClient(): " << e.what());

        return -1;
    }

    return 0;
}

void ANPRCameraCallbackImpl(int port, const char *lpNumber, const char* imagePath, void *context)
{
    LOG4CXX_INFO(logger, "ANPRCameraCallbackImpl(): port: " << port << ", lpNumber: " << lpNumber << ", imagePath: " << imagePath);

    SendToIceClient(port, lpNumber, imagePath);
}

int StartIceServer()
{
    anprCamera = new ANPRCamera;
    anprCamera->callback = ANPRCameraCallbackImpl;

    configPath = boost::filesystem::initial_path<boost::filesystem::path>().string() + "/UVSSVideoCamera.xml";

    LOG4CXX_INFO(logger, "StartIceServer(): Config file: " << configPath);

    try
    {
        Ice::CommunicatorPtr ich = Ice::initialize();

        XmlConfig config;
        std::string iceIPAddress = "127.0.0.1";
        int icePort = 10000;

        if (config.Open(configPath.c_str()))
        {
            if (config.GetIntValue("OutputPath", "Enable", 0) == 1)
            {
                anprCamera->enableOutputPath = true;
                anprCamera->outputPath = config.Get("OutputPath", "Path", "");

                LOG4CXX_INFO(logger, "StartIceServer(): ANPRCamera::outputPath: " << anprCamera->outputPath);
            }

            iceIPAddress = config.Get("RPC", "UVSSVideoCameraIceIPAddress", "127.0.0.1");
            icePort = config.GetIntValue("RPC", "UVSSVideoCameraIcePort", 10002);
        }

        LOG4CXX_INFO(logger, "StartIceServer(): Ice IP Address: " << iceIPAddress << ", Ice port: " << icePort);

        Ice::ObjectAdapterPtr adapter = ich->createObjectAdapterWithEndpoints("UVSSVideoCamera", "default -p " + std::to_string(icePort));

        Ice::ObjectPtr object = new UVSSVideoCameraI;

        adapter->add(object, adapter->getCommunicator()->stringToIdentity("UVSSVideoCamera"));
        adapter->activate();

        ich->waitForShutdown();
        ich->destroy();
    }
    catch (const Ice::SocketException& e)
    {
        std::cerr << e.what() << std::endl;
        LOG4CXX_ERROR(logger, std::string(e.what()));

        return -1;
    }

    return 0;
}

void ANPRCameraCallbackTest(int handle, unsigned char *buffer, int size, int width, int height, int type, void *context)
{
    IplImage *image = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);

    if (type == 1 || type == 2) // YV12
    {
        IplImage *yuvImage = cvCreateImage(cvSize(width, height + height / 2), IPL_DEPTH_8U, 1);

        memcpy(yuvImage->imageData, buffer, size);
        cvCvtColor(yuvImage, image, CV_YUV2RGB_YV12);

        if (type == 2)
            cvCvtColor(image, image, CV_BGR2RGB);

        cvReleaseImage(&yuvImage);
    }
    else
    {
        memcpy(image->imageData, buffer, size);
    }

    if (!imageSaved)
    {
        cv::imwrite("frame.jpg", cv::cvarrToMat(image));

        imageSaved = true;
    }

    std::string plateNumber = anpr->DetectLPNumber(image);

    if (plateNumber != "")
    {
        std::cout << plateNumber << std::endl;
    }

    cvReleaseImage(&image);
}

#ifdef __linux__
int GBK2UTF8(char* src, size_t* srclen, char* dest, size_t* destlen)
{
    iconv_t cd = iconv_open("UTF-8", "GBK");

    if (cd == (iconv_t)-1) {
        printf("iconv_open() err : %s\n", strerror(errno));
        return -1;
    }

    size_t rc = iconv(cd, &src, srclen, &dest, destlen);

    if (rc == (size_t)-1) {
        printf("iconv() err : %s\n", strerror(errno));
        return -1;
    }

    iconv_close(cd);

    return 0;
}
#endif

void ANPRInfoCallbackTest(int port, const char *licensePlateNumber, unsigned char* buffer, int size, int width, int height, void *context)
{
#ifdef __linux__
    char licensePlateNumberUTF8[32];
    size_t srclen = strlen(licensePlateNumber);
    size_t destlen = sizeof(licensePlateNumberUTF8);
    memset(licensePlateNumberUTF8, 0, 32);

    GBK2UTF8((char *)licensePlateNumber, &srclen, licensePlateNumberUTF8, &destlen);

    std::cout << "Port" << port << ": " << licensePlateNumberUTF8 << std::endl;
#else
    std::cout << "Port" << port << ": " << licensePlateNumber << std::endl;
#endif
}

// --init-anpr-camera --camera-index 0 --window-id 0
// --open-anpr-camera --camera-index 0
// --save-anpr-image --camera-index 0 --anpr-mode 0 --image-path "image.jpg"
// --start-save-video --camera-index 0 --video-path "video.mp4"
// --stop-save-video --camera-index 0
void TestIceServer(int operation)
{
    Ice::CommunicatorPtr uvssIch = 0;
    Ice::ObjectAdapterPtr uvssAdapter = 0;
    Ice::ObjectPtr uvssObject = 0;

    configPath = boost::filesystem::initial_path<boost::filesystem::path>().string() + "/UVSSVideoCamera.xml";

    try
    {
        std::string uvssVideoCameraIPAddress = "127.0.0.1";
        std::string uvssIPAddress = "127.0.0.1";
        int uvssVideoCameraPort = 10000;
        int uvssPort = 10001;

        XmlConfig config;

        if (config.Open(configPath.c_str()))
        {
            uvssVideoCameraIPAddress = config.Get("RPC", "UVSSVideoCameraIceIPAddress", "127.0.0.1");
            uvssIPAddress = config.Get("RPC", "UVSSIceIPAddress", "127.0.0.1");
            uvssVideoCameraPort = config.GetIntValue("RPC", "UVSSVideoCameraIcePort", 10002);
            uvssPort = config.GetIntValue("RPC", "UVSSIcePort", 10001);
        }

        // UVSS
        Ice::PropertiesPtr props = Ice::createProperties();
        props->setProperty("Ice.MessageSizeMax", "100000");
        Ice::InitializationData initData;
        initData.properties = props;
        uvssIch = Ice::initialize(initData);

        uvssAdapter = uvssIch->createObjectAdapterWithEndpoints("UVSS", "default -p " + std::to_string(uvssPort));

        uvssObject = new UVSSI;

        uvssAdapter->add(uvssObject, uvssAdapter->getCommunicator()->stringToIdentity("UVSS"));
        uvssAdapter->activate();

        // UVSSVideoCamera
        Ice::CommunicatorPtr ich = Ice::initialize();

        auto base = ich->stringToProxy("UVSSVideoCamera:tcp -h " + uvssVideoCameraIPAddress + " -p " + std::to_string(uvssVideoCameraPort));

        auto proxy = UVSSIce::UVSSVideoCameraPrx::uncheckedCast(base);

        std::cout << typeid(proxy).name() << std::endl;

        if (!proxy)
        {
            throw std::runtime_error("Invalid UVSSVideoCamera proxy");
        }

        switch (operation)
        {
            case INIT_ANPR_CAMERA:
                proxy->InitANPRCamera(cameraIndex, windowId);
                break;
            case OPEN_ANPR_CAMERA:
                proxy->OpenANPRCamera(cameraIndex);
                break;
            case SAVE_ANPR_IMAGE:
                proxy->SaveANPRImage(cameraIndex, anprMode, imagePath);

                // Wait for callback
#ifdef _WIN32
                Sleep(5000);
#else
                sleep(5);
#endif
                break;
            case START_SAVE_VIDEO:
                proxy->StartSaveVideo(cameraIndex, videoPath);
#ifdef _WIN32
                Sleep(1000);
#else
                sleep(1);
#endif
                break;
            case STOP_SAVE_VIDEO:
                proxy->StopSaveVideo(cameraIndex);
#ifdef _WIN32
                Sleep(1000);
#else
                sleep(1);
#endif
                break;
        }

        uvssIch->destroy();
        ich->destroy();
    }
    catch (const std::exception& e)
    {
        std::cout << e.what() << std::endl;
    }
}

// --test-anpr-camera --camera-type 0 --camera-path 'rtsp://admin:admin@192.168.1.98:554' --window-id 0
// --test-anpr-camera --camera-type 1 --camera-path /dev/video0 --window-id 0
// --test-anpr-camera --camera-type 2 --camera-ip-address 192.168.1.98 --camera-username admin --camera-password admin --window-id 0
void TestANPRCamera(int cameraType, std::string ipAddress, std::string username, std::string password, int windowId, std::string path)
{
#ifdef _WIN32
    HWND hwnd = GetConsoleWindow();
    std::cout << "HWND: " << (int)hwnd << std::endl;
#endif

    anpr = new ANPRTH;

    int ret = anpr->Init(0, 60, 400, 1920, 1200);

    std::cout << "ANPRTH::Init(): " << ret << std::endl;

    CameraBase *camera = 0;

    switch(cameraType)
    {
        case 0:
            camera = new FFmpegCamera;
#ifdef _WIN32
            ((FFmpegCamera *)camera)->Init((HWND)windowId, path.c_str());
#else
            ((FFmpegCamera *)camera)->Init(windowId, path.c_str());
#endif
            camera->SetCallback(ANPRCameraCallbackTest, 0);
            break;
        case 1:
            camera = new FFmpegCamera;
            ((FFmpegCamera *)camera)->device = true;
#ifdef _WIN32
            ((FFmpegCamera *)camera)->Init((HWND)windowId, path.c_str());
#else
            ((FFmpegCamera *)camera)->Init(windowId, path.c_str());
#endif
            camera->SetCallback(ANPRCameraCallbackTest, 0);
            break;
        case 2:
            camera = new HikCamera;
#ifdef _WIN32
            ((HikCamera *)camera)->Init((HWND)windowId, ipAddress.c_str(), 8000, username.c_str(), password.c_str(), 0, 0);
#else
            ((HikCamera *)camera)->Init(windowId, ipAddress.c_str(), 8000, username.c_str(), password.c_str(), 0, 0);
#endif

            camera->SetCallback(ANPRCameraCallbackTest, 0);
            ((HikCamera *)camera)->SetANPRInfoCallback(ANPRInfoCallbackTest, 0);
            break;
    }

    ret = camera->Connect();

    std::cout << "CameraBase::Connect(): " << ret << std::endl;

#ifdef _WIN32
    Sleep(60000);
#else
    sleep(60);
#endif
}

int main(int argc, char* argv[])
{
#ifdef __linux__
    setlocale(LC_CTYPE, "C.UTF-8");
#endif

    std::string programPath;
#ifdef _WIN32
    char temp[MAX_PATH];
    GetModuleFileName(NULL, temp, MAX_PATH);

    programPath = temp;
    programPath = programPath.substr(0, programPath.find_last_of('\\'));
#elif __linux__
    char temp[PATH_MAX];
    memset(temp, 0, PATH_MAX);
    readlink("/proc/self/exe", temp, PATH_MAX - 1);
    programPath = temp;
    programPath = programPath.substr(0, programPath.find_last_of('/'));
#else
    programPath = boost::filesystem::initial_path<boost::filesystem::path>().string();
#endif

    std::string configPath = programPath + "/UVSSVideoCamera.properties";
    log4cxx::PropertyConfigurator::configure(configPath.c_str());

    LOG4CXX_INFO(logger, "UVSSVideoCamera");

    int operation = 0;
    int cameraType = 0;
    std::string ipAddress = "127.0.0.1";
    std::string username = "";
    std::string password = "";
    std::string path = "";

    while (1)
    {
        static struct option long_options[] =
        {
            {"init-anpr-camera", no_argument, NULL, INIT_ANPR_CAMERA},
            {"open-anpr-camera", no_argument, NULL, OPEN_ANPR_CAMERA},
            {"save-anpr-image", no_argument, NULL, SAVE_ANPR_IMAGE},
            {"test-anpr-camera", no_argument, NULL, TEST_ANPR_CAMERA},
            {"camera-index", required_argument, NULL, CAMERA_INDEX},
            {"window-id", required_argument, NULL, WINDOW_ID},
            {"anpr-mode", required_argument, NULL, ANPR_MODE},
            {"image-path", required_argument, NULL, IMAGE_PATH},
            {"camera-type", required_argument, NULL, CAMERA_TYPE},
            {"camera-ip-address", required_argument, NULL, CAMERA_IP_ADDRESS},
            {"camera-username", required_argument, NULL, CAMERA_USERNAME},
            {"camera-password", required_argument, NULL, CAMERA_PASSWORD},
            {"camera-path", required_argument, NULL, CAMERA_PATH},
            {"start-save-video", no_argument, NULL, START_SAVE_VIDEO},
            {"stop-save-video", no_argument, NULL, STOP_SAVE_VIDEO},
            {"video-path", required_argument, NULL, VIDEO_PATH},
            {0, 0, 0, 0}
        };

        int option_index = 0;

        int c = getopt_long(argc, argv, "", long_options, &option_index);

        if (c == -1)
            break;

        switch (c)
        {
        case INIT_ANPR_CAMERA:
            operation = INIT_ANPR_CAMERA;
            break;
        case OPEN_ANPR_CAMERA:
            operation = OPEN_ANPR_CAMERA;
            break;
        case SAVE_ANPR_IMAGE:
            operation = SAVE_ANPR_IMAGE;
            break;
        case TEST_ANPR_CAMERA:
            operation = TEST_ANPR_CAMERA;
            break;
        case CAMERA_INDEX:
            cameraIndex = std::stoi(optarg);
            break;
        case WINDOW_ID:
            windowId = std::stoi(optarg);
            break;
        case ANPR_MODE:
            anprMode = std::stoi(optarg);
            break;
        case IMAGE_PATH:
            imagePath = optarg;
            break;
        case CAMERA_TYPE:
            cameraType = std::stoi(optarg);
            break;
        case CAMERA_IP_ADDRESS:
            ipAddress = optarg;
            break;
        case CAMERA_USERNAME:
            username = optarg;
            break;
        case CAMERA_PASSWORD:
            password = optarg;
            break;
        case CAMERA_PATH:
            path = optarg;
            break;
        case VIDEO_PATH:
            videoPath = optarg;
            break;
        case START_SAVE_VIDEO:
            operation = START_SAVE_VIDEO;
            break;
        case STOP_SAVE_VIDEO:
            operation = STOP_SAVE_VIDEO;
            break;
        }
    }

    switch (operation)
    {
        case INIT_ANPR_CAMERA:
            TestIceServer(INIT_ANPR_CAMERA);
            break;
        case OPEN_ANPR_CAMERA:
            TestIceServer(OPEN_ANPR_CAMERA);
            break;
        case SAVE_ANPR_IMAGE:
            TestIceServer(SAVE_ANPR_IMAGE);
            break;
        case START_SAVE_VIDEO:
            TestIceServer(START_SAVE_VIDEO);
            break;
        case STOP_SAVE_VIDEO:
            TestIceServer(STOP_SAVE_VIDEO);
            break;
        case TEST_ANPR_CAMERA:
            TestANPRCamera(cameraType, ipAddress, username, password, windowId, path);
            break;
        default:
            StartIceServer();
    }

    return 0;
}
